#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <sys/time.h>
#include <pthread.h>
#include <signal.h>

/*
int pthread_create(pthread_t *tid, const pthread_attr_t *attr, void *(*func) (void *), void *arg);
int pthread_join (pthread_t tid, void ** status);
pthread_t pthread_self (void);
int pthread_detach (pthread_t tid);
void pthread_exit (void *status);
int pthread_mutex_lock(pthread_mutex_t * mptr); 
int pthread_mutex_unlock(pthread_mutex_t * mptr);
pthread_mutex_init(&mutex,NULL);
pthread_mutex_destroy(&mutex);
*/
pthread_mutex_t mutex;
int flag;
static int GetFlag(){
	int val;
	pthread_mutex_lock(&mutex);
	val=flag;
	pthread_mutex_unlock(&mutex);
	return val;
}
static void SetFlag(int val){
	pthread_mutex_lock(&mutex);
	flag=val;
	pthread_mutex_unlock(&mutex);
}
void BlockSigno(int signo)
{
    sigset_t signal_mask;
    sigemptyset(&signal_mask);
    sigaddset(&signal_mask, signo);
    pthread_sigmask(SIG_BLOCK, &signal_mask, NULL);
}
enum{
	FLAG_TASK1_EXIT=1,
	FLAG_TASK2_EXIT,
	FLAG_TASKALL_EXIT,
	FLAG_REMOTE,
	FLAG_LISTEN,
	FLAG_LISTEN_START,
	FLAG_LISTEN_END,
	FLAG_NULL,
	FLAG_CHECK_OK,
	FLAG_KEEPLINK,
	FLAG_TEST_PORT,
};
/////////////

//char* SERVERIP = "127.0.0.1";
// char *N2NSERVERIP="151.11.50.180";//"supernode.ntop.org";
char *N2NSERVERIP="220.181.38.148";//"baidu.com";
#define N2NSERVERPORT 80
char* HELLOWORD="hello\n";
char* hellotcp="ht123456";
char* remoteip="127.0.0.1";
int remoteport=65534;
int localport=12345;
int testport=0;
int keepalive=0;

#define TIMEOUT1 50000
#define SOCKOPT_1 SO_REUSEADDR //SO_REUSEADDR|SO_REUSEPORT
//////////static
static char* checkdata;
#define ERR_EXIT(m) \
    do \
{ \
    perror(m); \
    exit(EXIT_FAILURE); \
    } while(0)
static void SetLocalPort(int port){
	localport=port;
}
static int GetLocalPort(){
	return localport;
}
static void SetRemoteIP(char* str){
	remoteip=str;
}
static char* GetRemoteIP(){
	return remoteip;
}
static void SetRemotePort(int port){
	remoteport=port;
}
static int GetRemotePort(){
	return remoteport;
}

static int GetTime(int t0){
	int t=time(NULL);
	return t-t0;
}
void printhex(unsigned char* buffer,int len){
	while(len>16){
		printhex(buffer,16);
		buffer+=16;
		len-=16;
	}
	for(int i=0;i<len;i++){
		fprintf(stderr,"%02x ",(unsigned char)buffer[i]);
	}
	for(int i=0;i<16-len;i++){
		fprintf(stderr,"   ");
	}
	for(int i=0;i<len;i++){
		switch(buffer[i]){
			case 0x00:
			case 0x09:
			case 0x0a:
			case 0x0d:
				fputc('.',stderr);
				break;
			default:
				fputc(buffer[i],stderr);
		}
	}
	fprintf(stderr,"\n");
}
static void SetCheckData(char* dat){
	checkdata=dat;
}
static char* GetCheckData(){
	return checkdata;
}
static int CheckData(char* str,int len){
	return !memcmp(str,GetCheckData(),len);
}

void printbuffer(char* buffer,int len){
	printhex(buffer,len);
}
int tcprecv(int sock){
	char buffer[64];
	int rc;
	int flag;
	flag=GetFlag();
	if(flag==FLAG_KEEPLINK){
		flag=MSG_PEEK;
	}else{
		flag=0;
	}
	fprintf(stderr,"tcprecv:\n");
	rc = recv(sock, buffer, sizeof(buffer), flag);
	if(rc==0){
		fprintf(stderr,"tcprecv: host closed\n");
	}
	else if(rc>0){
		fprintf(stderr,"tcprecv: recv ok len=%d\n",rc);
		printbuffer(buffer,rc);
		if(CheckData(buffer,rc)){
			SetFlag(FLAG_KEEPLINK);
		}
	}
	else{
		fprintf(stderr,"tcprecv: error rc=%d,errno=%d\n",rc,errno);
	}
	return rc;
}
int tcpsendhello(int sock){
	int t;
	char temp[64];
	int len;
	fprintf(stderr,"hello:\n");
	t=GetTime(0);
	len=sprintf(temp,"%s:%d\n",hellotcp,t);
	SetCheckData(temp);
	send(sock, temp, len+1, 0);
	usleep(100000);
	fprintf(stderr,"hello:%s",temp);
}
int tcpexcept(int sock){
	fprintf(stderr,"tcpexcept\n");
}
int opensock(int port){
	int sock;
	if ((sock = socket(PF_INET, SOCK_STREAM|SOCK_NONBLOCK, 0)) < 0){
		ERR_EXIT("socket");
	}
	if(port){
		struct sockaddr_in servaddr;
		int on=1;
		if(setsockopt(sock,SOL_SOCKET,SOCKOPT_1,&on,sizeof(int))<0){
			ERR_EXIT("setsockopt error");
		}
		memset(&servaddr, 0, sizeof(servaddr));
		servaddr.sin_family = AF_INET;
		servaddr.sin_port = htons(port);
		servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
		if (bind(sock, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0){
			ERR_EXIT("bind error");
		}
	}
	return sock;
}
void closesock(int sock){
	//shutdown(sock,SHUT_RDWR);
	close(sock);
}
int testconnect(int localport,char* ipaddr,int remoteport){
	int sock;
	struct sockaddr_in servaddr;
	int ev=0;
	int connResult;
	fd_set fdsr,fdsw,fdse;
	struct timeval timeout;
	
	fprintf(stderr,"connect: %s:%d\n",ipaddr,remoteport);
	sock=opensock(localport); 
	memset(&servaddr, 0, sizeof(servaddr));
	servaddr.sin_family = AF_INET;
	servaddr.sin_addr.s_addr = inet_addr(ipaddr);
	servaddr.sin_port = htons(remoteport);
	connResult = connect(sock, (struct sockaddr *)&servaddr, sizeof(servaddr));
	if(connResult==0){
		fprintf(stderr,"connect: ok\n");
	}else{
		if((errno==EINPROGRESS)||(errno==EINTR)){
			timeout.tv_sec = 0;
			timeout.tv_usec = TIMEOUT1;
			//FD_ZERO(&fdsr);
			//FD_SET(sock, &fdsr);
			FD_ZERO(&fdsw);
			FD_SET(sock, &fdsw);
			//FD_ZERO(&fdse);
			//FD_SET(sock, &fdse);
			ev = select(FD_SETSIZE, NULL, &fdsw, NULL, &timeout);
			if(ev>0){
				fprintf(stderr,"connect: ok,port=%d,ev=%d\n",remoteport,ev);
				//if(FD_ISSET(sock, &fdsr)){
				//	tcprecv(sock);
				//}
				if(FD_ISSET(sock, &fdsw)){
					tcpsendhello(sock);
				}
				//if(FD_ISSET(sock, &fdse)){
				//	tcpexcept(sock);
				//}
			}
		}else{
			ERR_EXIT("connect: error");
		}
	}
	closesock(sock);
	return ev;
}

int tcplisten(int localport){
	int sock;
	int rc=0;
	int connResult;
	struct timeval timeout;
	fd_set fdsr;
	struct sockaddr_storage from;
	socklen_t fromlen;
	char buffer[64];
	int flag;
	fprintf(stderr,"tcplisten: %d\n",localport);
	sock=opensock(localport);
	rc=listen(sock,5);
	if(rc!=0){
		ERR_EXIT("tcplisten: error");
	}
	SetFlag(FLAG_LISTEN);
	while(1){
		flag=GetFlag();
		if((flag!=FLAG_LISTEN)&&(flag!=FLAG_KEEPLINK)){
			break;
		}
		timeout.tv_sec = 0;
		timeout.tv_usec = TIMEOUT1;
		FD_ZERO(&fdsr);
		FD_SET(sock, &fdsr);
		rc = select(FD_SETSIZE, &fdsr, NULL, NULL, &timeout);
		if(rc>0){
			int s;
			fprintf(stderr,"tcplisten: select rc=%d\n",rc);
			fromlen = sizeof(from);
			s = accept(sock, (struct sockaddr *)&from, &fromlen);
			if (s == -1) {
				fprintf(stderr,"tcplisten: accept errno=%d\n",errno);
			}else{
				rc = recv(s, buffer, sizeof(buffer), 0);
				if(rc>0){
					fprintf(stderr,"tcplisten: recv ok len=%d\n",rc);
					printbuffer(buffer,rc);
					if(CheckData(buffer,rc)){
						if(keepalive){
							SetFlag(FLAG_KEEPLINK);
						}else{
							fprintf(stdout,"%d\n",GetRemotePort());
							SetFlag(FLAG_TASKALL_EXIT);
						}
					}
				}else{
					fprintf(stderr,"tcplisten: recv rc=%d,errno=%d\n",rc,errno);
				}
			}
		}
	}
	closesock(sock);
	fprintf(stderr,"tcplisten end\n");
	SetFlag(FLAG_LISTEN_END);
	return rc;
}

void* task1(void *arg){
	int flag;
	int result=0;
	BlockSigno(SIGPIPE);
	while(1){
		flag=GetFlag();
		switch(flag){
			case FLAG_TASK1_EXIT:
				pthread_exit(0);
				return 0;
			break;
			case FLAG_TASKALL_EXIT:
				pthread_exit(0);
				return 0;
			break;
			case FLAG_LISTEN_START:
				tcplisten(GetLocalPort());
			break;
			default:
				usleep(50000);
			break;
		}
	}
}
void* task2(void *arg){
	int flag;
	int result=0,rc;
	char* ipaddr=GetRemoteIP();
	int localport=GetLocalPort();
	int remoteport=GetRemotePort();
	struct timeval check_time;
	struct timeval check_interval;
	struct timeval now;
	BlockSigno(SIGPIPE);
	while(1){
		flag=GetFlag();
		switch(flag){
			case FLAG_REMOTE:
			case FLAG_LISTEN_END:
				testconnect(localport,N2NSERVERIP,N2NSERVERPORT);
				SetFlag(FLAG_LISTEN_START);
				gettimeofday(&now, NULL);
				check_interval.tv_sec = 10;
				check_interval.tv_usec = 0;
				timeradd(&now, &check_interval, &check_time);
			break;
			case FLAG_LISTEN:
				gettimeofday(&now, NULL);
				if (timercmp(&now, &check_time, >)) {
					SetFlag(FLAG_NULL);
				}else{
					SetRemotePort(remoteport);
					rc=testconnect(0,ipaddr,remoteport);
					if(GetFlag()!=FLAG_KEEPLINK){
						if(remoteport--<=1){
							SetFlag(FLAG_TASKALL_EXIT);
						}
					}
				}
			break;
			case FLAG_KEEPLINK:
				sleep(30);
				rc=testconnect(0,ipaddr,remoteport);
			break;
			case FLAG_TEST_PORT:
				rc=testconnect(0,ipaddr,remoteport);
				if(rc==1){
					printf("ok\n");
				}else{
					printf("error\n");
				}
				SetFlag(FLAG_TASKALL_EXIT);
			break;
			case FLAG_TASK2_EXIT:
				pthread_exit(0);
				return 0;
			break;
			case FLAG_TASKALL_EXIT:
				pthread_exit(0);
				return 0;
			break;
			default:
				usleep(50000);
			break;
		}
	}
}
//serverport clientport
int main(int argc,char* argv[])
{
	pthread_t th1,th2;
	if(argc>=2){
		SetRemoteIP(argv[1]);
	}
	if(argc>=3){
		int port;
		sscanf(argv[2],"%d",&port);
		SetLocalPort(port);
	}
	if(argc>=4){
		int port;
		sscanf(argv[3],"%d",&port);
		SetRemotePort(port);
		testport=1;
	}
	if(argc>=5){
		int port;
		sscanf(argv[4],"%d",&port);
		SetRemotePort(port);
		testport=0;
		keepalive=1;
	}
	//if(keepalive)shakehand();
	if(testport){
		pthread_mutex_init(&mutex,NULL);
		SetFlag(FLAG_TEST_PORT);
		pthread_create(&th2,NULL,task2,NULL);
		pthread_join(th2,NULL);
		pthread_mutex_destroy(&mutex);
	}else{
		pthread_mutex_init(&mutex,NULL);
		SetFlag(FLAG_REMOTE);
		pthread_create(&th1,NULL,task1,NULL);
		pthread_create(&th2,NULL,task2,NULL);
		pthread_join(th2,NULL);
		pthread_mutex_destroy(&mutex);
	}
	return 0;
}
